package edu.kit.csl.pisa.io;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

import edu.kit.csl.pisa.datatypes.Dictionary;
import edu.kit.csl.pisa.models.AlignmentModel;
import edu.kit.csl.pisa.models.Model3;
import edu.kit.csl.pisa.models.SimpleModel;
import edu.kit.csl.pisa.ui.Configuration;

/**
 * This class is a concrete factory in the GoF design pattern Abstract Factory.
 * It loads the model from GIZA++' output files - i.e. lexical translation
 * table, fertility and distortion probabilities, and p_0 and p_1. From this,
 * it produces a {@link Model3} instance which can be used to initialize
 * parameters of the *-phone models.
 * 
 * @see Model3
 */
public class GizaModelFactory implements ModelFactory {

	/*
	 * GIZA++ file extensions.
	 */
	private final String GIZA_SRC_VCB_FILE = ".trn.src.vcb";
	private final String GIZA_TRGT_VCB_FILE = ".trn.trg.vcb";
	private final String GIZA_T_FILE = ".t3.final";
	//private final String GIZA_N_FILE = ".n3.final";
	@SuppressWarnings("unused")
	private final String GIZA_D_FILE = ".d3.final";
	private final String GIZA_P0_FILE = ".p0_3.final";
	
	/*
	 * From {@link Configuration}.
	 */
	int maxSrcSenLength;
	int maxTrgtSenLength;
	
	/*
	 * HashMap translating GIZA++ word-ids to our word-ids.
	 */
	private HashMap<Integer, Integer> srcVcb;
	private HashMap<Integer, Integer> trgtVcb;
	
	/*
	 * Model 3 parameters
	 */
	private double[][] t, n;
	private double[][][][] d;
	private double p1;
	
	/* (non-Javadoc)
	 * There is a implementation of this class for each file format. The
	 * function interpretLine() is called on each line of the file. After
	 * that, we can get the loaded model parameter with a function
	 * getParameter(). Since the return type of getParameter() differ among
	 * the subclasses, it is not implemented here.
	 */
	abstract private class LineInterpreter {
		/* (non-Javadoc)
		 * Interprets a file line.
		 * 
		 * @param line string array containing space separated values
		 */
		abstract public void interpretLine(String[] vals);
	}
	
	/* (non-Javadoc)
	 * Line interpreters for parameters t, n, d, p1
	 */
	private class TLineInterpreter extends LineInterpreter {
		@Override
		public void interpretLine(String[] vals) {
			t[srcVcb.get(Integer.parseInt(vals[0]))]
					[trgtVcb.get(Integer.parseInt(vals[1]))]
							= Math.log(Double.parseDouble(vals[2]));
		}
	}
	
	@SuppressWarnings("unused")
	private class DLineInterpreter extends LineInterpreter {
		private boolean alreadyComplained = false;
		@Override
		public void interpretLine(String[] vals) {
			d = new double[maxSrcSenLength][maxTrgtSenLength]
					[maxSrcSenLength][maxTrgtSenLength];
			if (Math.max(Integer.parseInt(vals[0]),
					Integer.parseInt(vals[2])) >= maxSrcSenLength
					|| Math.max(Integer.parseInt(vals[1]),
							Integer.parseInt(vals[3])) >= maxTrgtSenLength) {
				if (!alreadyComplained) {
					Logger.getSingleton().error("The GIZA++'s distortion model"
							+ " does not match our maximum sentence lengths.");
					alreadyComplained = true;
				}
			} else {
				d[Integer.parseInt(vals[0])]
					[Integer.parseInt(vals[1])]
						[Integer.parseInt(vals[2])]
							[Integer.parseInt(vals[3])] = Math
								.log(Double.parseDouble(vals[4]));
			}
		}
	}
	
	private class P0LineInterpreter extends LineInterpreter {
		@Override
		public void interpretLine(String[] vals) {
			p1 = Math.log(1 - Double.parseDouble(vals[0]));
		}
	}
	
	/* (non-Javadoc)
	 * Line interpreter for loading *Vcb (translating giza++ word-ids to our
	 * word-ids.
	 */
	private class VcbLineInterpreter extends LineInterpreter {
		private HashMap<Integer, Integer> map;
		private Dictionary dict;
		public VcbLineInterpreter(HashMap<Integer, Integer> map,
				Dictionary dict) {
			this.map = map;
			this.dict = dict;
		}
		@Override
		public void interpretLine(String[] vals) {
			map.put(Integer.parseInt(vals[0]), dict.get(vals[1]));
		}
	}
	
	/* (non-Javadoc)
	 * Loads a file and applies a line interpreter to each line.
	 * 
	 * @param fileName path to the file
	 * @param interpreter line interpreter
	 */
	private void loadFile(String fileName, LineInterpreter interpreter) 
			throws IOException {
		FileInputStream fstream = new FileInputStream(fileName);
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String line;
		// Read File Line By Line
		while ((line = br.readLine()) != null) {
			interpreter.interpretLine(line.split(" "));
		}
		in.close();
	}
	
	/*
	 * Allocate memory for t, n, d parameters.
	 */
	private void allocateMemory() {
		final int srcDictSize = 
				Dictionary.getSingleton(Dictionary.SRC_DICT).size();
		final int trgtDictSize = 
				Dictionary.getSingleton(Dictionary.TRGT_DICT).size();
		maxSrcSenLength = 
				Configuration.getSingleton().getInteger("maxSrcSenLen");
		maxTrgtSenLength = 
				Configuration.getSingleton().getInteger("maxTrgtSenLen");
		// Allocate memory
		t = new double[srcDictSize][trgtDictSize];
		d = new double[maxSrcSenLength][maxTrgtSenLength]
						[maxSrcSenLength][maxTrgtSenLength];
	}
	
	/**
	 * Creates a {@link Model3} instance from GIZA++ output files. The path to
	 * these files is taken from the configuration parameter initialModelPath.
	 * 
	 * @param name name of the model (normally "initialModel")
	 * @return an IBM model 3 alignment model. If an error occurred, the model
	 * 			may be empty and a fatalError (see {@link Logger#FATAL_ERROR})
	 * 			is logged
	 */
	@Override
	public AlignmentModel createModel(String name) {
		String initialModelPath = Configuration.getSingleton().getString(
				"initialModelPath");
		srcVcb = new HashMap<Integer, Integer>();
		trgtVcb = new HashMap<Integer, Integer>();
		srcVcb.put(0, 0); // NULL word
		try {
			loadFile(initialModelPath + GIZA_SRC_VCB_FILE,
					new VcbLineInterpreter(srcVcb,
							Dictionary.getSingleton(Dictionary.SRC_DICT)));
			loadFile(initialModelPath + GIZA_TRGT_VCB_FILE,
					new VcbLineInterpreter(trgtVcb,
							Dictionary.getSingleton(Dictionary.TRGT_DICT)));
			allocateMemory();
			loadFile(initialModelPath + GIZA_T_FILE, new TLineInterpreter());
			//loadFile(initialModelPath + GIZA_N_FILE, new NLineInterpreter());
			//loadFile(initialModelPath + GIZA_D_FILE, new DLineInterpreter());
			loadFile(initialModelPath + GIZA_P0_FILE, new P0LineInterpreter());
			return new Model3(name, t, n, d, p1);
		} catch (ArrayIndexOutOfBoundsException e) {
			Logger.getSingleton().fatalError("One of the GIZA++ model files "
					+ "does not have the expected number of columns or "
					+ "exceeds the max*SenLen parameters: "
					+ e.getMessage());
		} catch (NumberFormatException e) {
			Logger.getSingleton().fatalError("Number format exception in one "
					+ " of the GIZA++ model files: " + e.getMessage());
		} catch (IOException e) {
			Logger.getSingleton().fatalError("IO Error while loading IBM "
					+ "Model 3 from GIZA++ output files. Please check the "
					+ "parameter initialModelPath: " + e.getMessage());
		}
		return new SimpleModel(name);
	}
}
